﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud

//#define DEBUG_RAISE_EVENTS
#define RECORD_CALLS

using System.Runtime.Serialization;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using BrainTechLLC.ThreadSafeObjects;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Reflection.Emit;
using System.Diagnostics;
using System.Threading;

#if USE_HYPER
using Hyper.ComponentModel;
#endif

namespace BrainTechLLC
{
    public class DebugSettingsInstance : BaseNotifyPropertyChanged
    {
        public static bool _ShowDebugStatic;

        protected bool _ShowDebug;

        public bool ShowDebug
        {
            get { return _ShowDebug; }
            set
            {
                if (_ShowDebug != value)
                {
                    _ShowDebug = value;
                    _ShowDebugStatic = value;
                    OnPropertyChanged("ShowDebug");
                }
            }
        }
    }

    public static class DebugSettings
    {
        //#if DEBUG_RAISE_EVENTS || RECORD_CALLS
        private static DebugSettingsInstance _DebugSettingsInstance;

        public static DebugSettingsInstance DebugSettingsInstance
        {
            get
            {
                if (_DebugSettingsInstance == null)
                {
                    Interlocked.CompareExchange(ref _DebugSettingsInstance, new DebugSettingsInstance(), null);
                }
                return _DebugSettingsInstance;
            }
        }
        //#endif
    }

#if SILVERLIGHT
#else

    // Copyright (c) 2008 Daniel Grunwald
    // 
    // Permission is hereby granted, free of charge, to any person
    // obtaining a copy of this software and associated documentation
    // files (the "Software"), to deal in the Software without
    // restriction, including without limitation the rights to use,
    // copy, modify, merge, publish, distribute, sublicense, and/or sell
    // copies of the Software, and to permit persons to whom the
    // Software is furnished to do so, subject to the following
    // conditions:
    // 
    // The above copyright notice and this permission notice shall be
    // included in all copies or substantial portions of the Software.
    // 
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    // OTHER DEALINGS IN THE SOFTWARE.
    /// <summary>
    /// A class for managing a weak event.
    /// </summary>
    public sealed class FastSmartWeakEvent<T> where T : class
    {
        public struct EventEntry
        {
            public readonly FastSmartWeakEventForwarderProvider.ForwarderDelegate Forwarder;
            public readonly MethodInfo TargetMethod;
            public readonly WeakReference TargetReference;

            public EventEntry(FastSmartWeakEventForwarderProvider.ForwarderDelegate Forwarder, MethodInfo targetMethod, WeakReference targetReference)
            {
                this.Forwarder = Forwarder;
                this.TargetMethod = targetMethod;
                this.TargetReference = targetReference;
            }
        }

        public readonly List<EventEntry> eventEntries = new List<EventEntry>();

        static FastSmartWeakEvent()
        {
            if (!typeof(T).IsSubclassOf(typeof(Delegate)))
                throw new ArgumentException("T must be a delegate type");
            MethodInfo invoke = typeof(T).GetMethod("Invoke");
            if (invoke == null || invoke.GetParameters().Length != 2)
                throw new ArgumentException("T must be a delegate type taking 2 parameters");
            ParameterInfo senderParameter = invoke.GetParameters()[0];
            if (senderParameter.ParameterType != typeof(object))
                throw new ArgumentException("The first delegate parameter must be of type 'object'");
            ParameterInfo argsParameter = invoke.GetParameters()[1];
            if (!typeof(EventArgs).IsAssignableFrom(argsParameter.ParameterType)) // ..IsSubclassOf(typeof(EventArgs)))
                throw new ArgumentException("The second delegate parameter must be derived from type 'EventArgs'");
            if (invoke.ReturnType != typeof(void))
                throw new ArgumentException("The delegate return type must be void.");
        }

        public void Add(T eh)
        {
            if (eh != null)
            {
                Delegate d = (Delegate)(object)eh;
                //if (eventEntries.Count == eventEntries.Capacity)
                if (eventEntries.Count > 20)
                {
                    RemoveDeadEntries();
                }
                WeakReference target = d.Target != null ? new WeakReference(d.Target) : null;
                eventEntries.Add(new EventEntry(FastSmartWeakEventForwarderProvider.GetForwarder(d.Method), d.Method, target));
            }
        }

        void RemoveDeadEntries()
        {
            eventEntries.RemoveAll(ee => ee.TargetReference != null && !ee.TargetReference.IsAlive);
        }

        public void Remove(T eh)
        {
            if (eh != null)
            {
                Delegate d = (Delegate)(object)eh;
                for (int i = eventEntries.Count - 1; i >= 0; i--)
                {
                    EventEntry entry = eventEntries[i];
                    if (entry.TargetReference != null)
                    {
                        object target = entry.TargetReference.Target;
                        if (target == null)
                        {
                            eventEntries.RemoveAt(i);
                        }
                        else if (target == d.Target && entry.TargetMethod == d.Method)
                        {
                            eventEntries.RemoveAt(i);
                            break;
                        }
                    }
                    else
                    {
                        if (d.Target == null && entry.TargetMethod == d.Method)
                        {
                            eventEntries.RemoveAt(i);
                            break;
                        }
                    }
                }
            }
        }

        public void Raise(object sender, EventArgs e)
        {
            bool needsCleanup = false;
#if DEBUG_RAISE_EVENTS
            if (DebugSettings._showDebug)
            {
                if (eventEntries.Count > 0)
                {
                    PropertyChangedEventArgs pe = e as PropertyChangedEventArgs;
                    if (pe != null)
                    {
                        Console.WriteLine(string.Format("{0} PropChange '{1}'", sender.ToString(), pe.PropertyName));
                    }
                }
            }
#endif
            foreach (EventEntry ee in eventEntries.ToArray())
            {
#if RECORD_CALLS
                if (DebugSettingsInstance._ShowDebugStatic)
                {
                    if (ee.TargetReference == null || ee.TargetReference.IsAlive)
                    {
                        object targetObject = ee.TargetReference == null ? null : ee.TargetReference.Target;
                        RecordedEventInfo.Instance.RecordEventInfo(sender, ee.TargetMethod, targetObject, e);
                    }
                }
#endif
#if DEBUG_RAISE_EVENTS
                if (DebugSettings._showDebug)
                {
                    string temp = string.Empty;
                    if (ee.TargetMethod != null)
                    {
                        temp = ee.TargetMethod.Name;
                    }
                    Console.WriteLine(string.Format(" to {0} - {1}", ee.TargetReference.Target.GetType().Name, temp));
                }
#endif
                needsCleanup |= ee.Forwarder(ee.TargetReference, sender, e);
            }
            if (needsCleanup)
                RemoveDeadEntries();
        }
    }

    public class RecordedEventInfo
    {
        public static RecordedEventInfo Instance = new RecordedEventInfo();

        public ThreadSafeListAndLookup<Type, ObjectEventInfo> EventInfos = new ThreadSafeListAndLookup<Type, ObjectEventInfo>();

        public ObjectEventInfo FindEventInfoForObject(object instance)
        {
            Type instanceType = instance.GetType();
            return FindEventInfoForObject(instance, instanceType);
        }

        public int Count
        {
            get { return EventInfos.Count; }
        }

        public ObjectEventInfo FindEventInfoForObject(object instance, Type t)
        {
            return EventInfos.Find(t, (call) => object.ReferenceEquals(instance, call));
        }

        public ObjectEventInfo FindOrAddEventInfoForObject(object instance, Type t)
        {
            ObjectEventInfo result = EventInfos.FindOrAdd(t,
                (eventInfo) => object.ReferenceEquals(eventInfo.ObjectInstance, instance),
                () => new ObjectEventInfo(instance, t));

            return result;
        }

        public List<ObjectEventInfo> GetAllEventInfosForType(Type t)
        {
            List<ObjectEventInfo> results = EventInfos.Find(t);

            return results;
        }

        public ObjectEventInfo RecordEventInfo(object instance, MethodInfo targetMethod, object target, EventArgs e)
        {
            if (instance == null)
                return null;

            Type instanceType = instance.GetType();

            ObjectEventInfo result = FindOrAddEventInfoForObject(instance, instanceType);
            result.AddEventCall(targetMethod, target, e);

            return result;
        }

        public void Clear()
        {
            EventInfos.Clear();
        }
    }

    public class EventCallInfo : BaseNotifyPropertyChanged
    {
        public Type EventArgsType;
        public object Target;
        public Type TargetType;
        public MethodInfo TargetMethod;
        public object AdditionalInfo;
        public object PropertyValueAtTime;
    }

    public class ObjectEventInfo
    {
        public Type Type;
        public object ObjectInstance;
        public ThreadSafeListAndLookup<Type, EventCallInfo> EventInfo = new ThreadSafeListAndLookup<Type, EventCallInfo>();

        public List<EventCallInfo> GetAllCallInfosForEventArgType(Type t)
        {
            return EventInfo.Find(t);
        }

        public void AddEventCall(MethodInfo targetMethod, object target, EventArgs e)
        {
            Type eventArgType = e.GetType();

            EventCallInfo callInfo = new EventCallInfo()
            {
                EventArgsType = eventArgType,
                Target = target,
                TargetType = target == null ? null : target.GetType(),
                TargetMethod = targetMethod
            };

            PropertyChangedEventArgs p = e as PropertyChangedEventArgs;
            if (p != null)
            {
                callInfo.PropertyValueAtTime = Type.GetProperty(p.PropertyName).GetValue(ObjectInstance, null);
                if (p != null)
                {
                    callInfo.AdditionalInfo = p.PropertyName;
                }
            }

            EventInfo.Add(eventArgType, callInfo);
        }

        public ObjectEventInfo(object o)
        {
            Type = o.GetType();
            ObjectInstance = o;
        }

        public ObjectEventInfo(object o, Type t)
        {
            Type = t;
            ObjectInstance = o;
        }
    }

    // The forwarder-generating code is in a separate class because it does not depend on type T.
    public static class FastSmartWeakEventForwarderProvider
    {
        public static readonly MethodInfo getTarget = typeof(WeakReference).GetMethod("get_Target");
        public static readonly Type[] forwarderParameters = { typeof(WeakReference), typeof(object), typeof(EventArgs) };
        public delegate bool ForwarderDelegate(WeakReference wr, object sender, EventArgs e);

        public static readonly Dictionary<MethodInfo, ForwarderDelegate> forwarders = new Dictionary<MethodInfo, ForwarderDelegate>();

        public static ForwarderDelegate GetForwarder(MethodInfo method)
        {
            lock (forwarders)
            {
                ForwarderDelegate d;
                if (forwarders.TryGetValue(method, out d))
                    return d;
            }

            if (method.DeclaringType.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length != 0)
                throw new ArgumentException("Cannot create weak event to anonymous method with closure.");

            Debug.Assert(getTarget != null);

            DynamicMethod dm = new DynamicMethod(
                "FastSmartWeakEvent", typeof(bool), forwarderParameters, method.DeclaringType);

            ILGenerator il = dm.GetILGenerator();

            if (!method.IsStatic)
            {
                il.Emit(OpCodes.Ldarg_0);
                il.EmitCall(OpCodes.Callvirt, getTarget, null);
                il.Emit(OpCodes.Dup);
                Label label = il.DefineLabel();
                il.Emit(OpCodes.Brtrue, label);
                il.Emit(OpCodes.Pop);
                il.Emit(OpCodes.Ldc_I4_1);
                il.Emit(OpCodes.Ret);
                il.MarkLabel(label);
            }
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldarg_2);
            il.EmitCall(OpCodes.Call, method, null);
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Ret);

            ForwarderDelegate fd = (ForwarderDelegate)dm.CreateDelegate(typeof(ForwarderDelegate));
            lock (forwarders)
            {
                forwarders[method] = fd;
            }
            return fd;
        }
    }
#endif
}

